package org.flashbrighton.as3bots.controllers
{	
	import Box2D.Collision.b2WorldManifold;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.Contacts.b2Contact;
	import Box2D.Dynamics.Contacts.b2ContactEdge;
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	import org.flashbrighton.as3bots.effects.Spark;
	import org.flashbrighton.as3bots.events.CollisionEvent;
	
	import flash.display.*;
	import flash.events.Event;
	
	import org.flashbrighton.as3bots.bots.*;
	import org.flashbrighton.as3bots.components.*;
	import org.flashbrighton.as3bots.events.BotEvent;
	import org.flashbrighton.as3bots.models.*;
	import org.flashbrighton.as3bots.views.*;
	import org.flashbrighton.as3bots.views.ui.Message;


	public class GameController extends Sprite
	{			
		private var _model:GameModel;
		private var _view:GameView;
		private var _bots:Vector.<BaseBot>;
		private var _botsByModel:Dictionary;
		private var _componentManager:ComponentManager;
		private var _scorer:CollisionScorer;
		private var _currentArenaWidth:Number;
		
		
		public function GameController(model:GameModel, view:GameView):void
		{
			_model = model;
			_view = view;
			init();
		}
		
		
		public function addBot(bot:BaseBot):void
		{
			var botModel:BotModel;
			
			// Add components
			for each (var s:String in bot.components)
			{
				_componentManager.addToBot(s, bot);
			}
			
			botModel = new BotModel(_model.physics.addCircle(0, 0, _componentManager.chassis.getRadiusFromSize(bot.size), _componentManager.chassis.density, _componentManager.chassis.friction, _componentManager.chassis.restitution));
			botModel.bot = bot;
			botModel.energy = HealthComponent.MAX_ENERGY;
			botModel.body.SetLinearDamping(_componentManager.chassis.linDamping);
			botModel.body.SetAngularDamping(_componentManager.chassis.angDamping);
			
			// Set reference back to model from body
			botModel.body.SetUserData(botModel);
			
			_bots.push(bot);
			_botsByModel[botModel] = bot;
			_model.addBot(botModel);
		}
		
		
		public function setupGame():void 
		{
			_currentArenaWidth = 10;
			createWalls(_currentArenaWidth);
			arrangeBots(_currentArenaWidth, _currentArenaWidth);
			addViews();
			_view.update();
			updateBotControllers();
			_model.timer.addEventListener(TimerEvent.TIMER, shrinkArena);
			_model.timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimeEnd);
			_model.timer.reset();
			_model.started = false;
		}
		
		
		public function startGame():void
		{
			_model.started = true;
			_model.isPlaying = true;
			_model.timer.start();
		}
		
		
		public function stopGame():void 
		{
			_model.isPlaying = false;
			_model.timer.stop();
		}
		
		
		public function pauseGame():void 
		{
			_model.isPlaying = false;
			_model.timer.stop();
		}
		
		
		public function resumeGame():void 
		{
			_model.isPlaying = true;
			_model.timer.start();
		}
		
		
		private function init():void
		{							
			_bots = new Vector.<BaseBot>;
			_botsByModel = new Dictionary(true);
			_componentManager = new ComponentManager();			
			_scorer = new CollisionScorer();
			_scorer.addEventListener(BotEvent.HIT, checkCollisions, false, 0, true);
			_model.physics.world.SetContactListener(_scorer);
			_model.isPlaying = false;
			addEventListener(Event.ENTER_FRAME, update, false, 0, true);
		}
		
		
		/**
		 * Creates the walls in a clockwise fashion from the top.
		 */
		private function createWalls(arenaWidth:Number):void 
		{
			var halfLength:Number = arenaWidth * 0.5;
			var depth:Number = 0.4;
			var halfDepth:Number = depth * 0.5;
			
			var top:WallModel = new WallModel(_model.physics.addBox(0, 0, halfLength, halfDepth));
			top.body.SetUserData(top);
			_model.addWall(top);
			
			var right:WallModel = new WallModel(_model.physics.addBox(0, 0, halfDepth, halfLength));
			right.body.SetUserData(right);
			_model.addWall(right);
			
			var bottom:WallModel = new WallModel(_model.physics.addBox(0, 0, halfLength, halfDepth));
			bottom.body.SetUserData(bottom);
			_model.addWall(bottom);
			
			var left:WallModel = new WallModel(_model.physics.addBox(0, 0, halfDepth, halfLength));
			left.body.SetUserData(left);
			_model.addWall(left);
			
			// Set positions
			top.body.SetPosition(new b2Vec2(halfLength + depth, halfDepth));
			right.body.SetPosition(new b2Vec2(halfLength * 2 - halfDepth, halfLength + depth));
			bottom.body.SetPosition(new b2Vec2(halfLength - depth, halfLength * 2 - halfDepth));
			left.body.SetPosition(new b2Vec2(halfDepth, halfLength - depth));
		}
		
		
		private function destroyWalls():void 
		{
			while (_model.walls.length > 0)
			{
				_model.removeWall(_model.walls[0]);
			}
		}
		
		
		private function arrangeBots(arenaWidth:Number, arenaHeight:Number):void
		{
			var halfWidth:Number = arenaWidth * 0.5;
			var halfHeight:Number = arenaHeight * 0.5;
			var n:int = 0;
			var	br:Number;
			var	bx:Number;
			var	by:Number;
			var	brad:Number;
			var	bot:BotModel;			
			var numBots:int = _model.botList.length;
			
			for each (bot in _model.botList)
			{
				br = (360 / numBots * n);
				brad = br * Math.PI / 180;
				bx = halfWidth + Math.cos(brad) * (halfWidth * 0.75);
				by = halfHeight + Math.sin(brad) * (halfHeight * 0.75);
				
				bot.body.SetPosition(new b2Vec2(bx, by));
				bot.body.SetAngle((br - 180) * Math.PI / 180);
					
				n++;
			}
		}
		
		
		private function addViews():void
		{
			var bot:BaseBot;
			var botModel:BotModel;
			
			_view.addArenaView();			
			
			for each (botModel in _model.botList) 
			{
				bot = botModel.bot;
				if (!bot.skin) bot.skin = _componentManager.chassis.createSkin(bot.size, bot.colour, bot.skin);
				_view.addBotView(bot.skin, botModel);
			}
			
			_view.setUI();
			_view.alert.addEventListener(Event.COMPLETE, alertCompleteHandler);
		}
		
		
		private function alertCompleteHandler(e:Event):void 
		{
			if (_model.started) resumeGame();
		}
		
		
		private function shrinkArena(e:TimerEvent):void 
		{
			var shrinkRate:Number = 0.25;
			var minWidth:Number = 3.0;
			var interval:Number = 20;
			var dangerTime:int = 20;
			var remaining:int = GameModel.MAX_TIME - _model.timer.currentCount;
			
			if (_currentArenaWidth <= minWidth) return;
			
			if (remaining == dangerTime)
			{
				pauseGame();
				_view.alert.blink("DANGER TIME");
			}
			else if (remaining % interval == 0 && remaining > dangerTime)
			{
				pauseGame();
				_view.alert.blink("SHRINKING ARENA");
			}
			
			if (remaining % interval == 0 || remaining <= dangerTime)
			{				
				destroyWalls();
				createWalls(_currentArenaWidth -= shrinkRate);
			}
		}
		
		
		/**
		 * The main loop.
		 */
		private function update(event:Event=null):void
		{	
			if (!_model.isPlaying) return;
			
			_model.updateWorld();
			//checkCollisions is called at this point
			_view.update();			
			updateValues();
			updateComponents();
			updateBotControllers();
			checkScores();
		}
		
		
		private function checkCollisions(e:CollisionEvent=null):void 
		{			
			var botModel:BotModel = e.botModel as BotModel;
			var bot:BaseBot = _botsByModel[botModel];
			var damage:int = int(e.damage);
			
			botModel.damage = damage;
			
			// Notify Bot
			bot.controller.notify(new BotEvent(e.type, e.collisionPoint, e.targetType));
			
			// Spark effect
			var spark:Spark = new Spark(0xFFFF00);			
			spark.x = botModel.body.GetPosition().x * GameView.SCALE_FACTOR - 15;
			spark.y = botModel.body.GetPosition().y * GameView.SCALE_FACTOR - 15;
			_view.explosions.addChild(spark);
		}
		
		
		private function updateValues():void
		{
			var bot:BaseBot;
			var botModel:BotModel;
			var enginePower:Number;
			var steeringPower:Number;
			
			for each (botModel in _model.botList) 
			{
				bot = _botsByModel[botModel];
				enginePower = bot.controller.getEnginePower();
				steeringPower = bot.controller.getSteeringPower();
				
				// Limits
				if (enginePower > 1) enginePower = 1;
				else if (enginePower < -1) enginePower = -1;
				if (steeringPower > 1) steeringPower = 1;
				else if (steeringPower < -1) steeringPower = -1;
			
				botModel.enginePower = enginePower;
				botModel.steeringPower = steeringPower;
			}
		}
		
		
		private function updateComponents():void
		{
			for each (var botModel:BotModel in _model.botList) 
			{
				_componentManager.updateBot(_botsByModel[botModel], botModel);
			}
		}
		
		
		private function updateBotControllers():void
		{
			var botModel:BotModel;
			var bot:BaseBot;
			
			for each (botModel in _model.botList) 
			{
				bot = _botsByModel[botModel];
				bot.controller.update(new BotData(botModel.energy / HealthComponent.MAX_ENERGY));
			}
		}
		
		
		private function checkScores():void
		{
			for each (var bot:BotModel in _model.botList)
			{
				if (bot.energy < 0)
				{
					_model.removeBot(bot);
				}
			}
			
			if (_model.botList.length == 1)
			{
				stopGame();
				_model.started = false;
				_model.updateWorld();
				_view.update();
				setWinner(_model.botList[0]);
			}
		}
		
		
		private function onTimeEnd(e:TimerEvent):void 
		{
			stopGame();
			_model.started = false;
			_model.updateWorld();
			_view.update();
			
			var strongest:BotModel = _model.botList[0];
			
			for each (var botModel:BotModel in _model.botList) 
			{
				if (botModel.energy > strongest.energy) strongest = botModel;
			}
			
			setWinner(strongest);
		}
		
		
		private function setWinner(botModel:BotModel):void
		{
			_view.alert.show(botModel.bot.botName.toUpperCase() + "\nWINS!");
		}
		
	}
}